import datetime
import difflib
import json
import os
import sys
import threading
import re
import time

from loguru import logger


getYMD = lambda: datetime.datetime.now().strftime('%Y%m%d')
getYMDHms3 = lambda: datetime.datetime.now().strftime('%Y%m%d_%H%M%S')


#### beg of  分布式锁 ####
def acquire_lock(redis, lock_key, acquire_timeout=10):
  """尝试获取分布式锁"""
  end = time.time() + acquire_timeout
  while time.time() < end:
    if redis.setnx(lock_key, "locked"):
      redis.expire(lock_key, 30)  # 设置锁有效期为30秒
      return True
    time.sleep(0.1)
  return False


def release_lock(redis, lock_key):
  """释放分布式锁（使用Lua脚本避免竞态条件）"""
  if redis.exists(lock_key):
    # 使用Lua脚本确保原子性
    script = """
              if redis.call("get", KEYS[1]) == ARGV[1] then
                  return redis.call("del", KEYS[1])
              else
                  return 0
              end
          """
    redis.eval(script, 1, lock_key, "locked")


#### end of  分布式锁 ####

# 单例模式的元类,但不要嵌套使用它来创建2个实例，会导致死锁
class SingletonMeta(type):
  _instance = {}
  _lock = threading.Lock()

  def __call__(cls, *args, **kwargs):
    key = (cls, str(args), frozenset((k, str(v) if isinstance(v, dict) else v) for k, v in kwargs.items()))
    with cls._lock:
      if key not in cls._instance:
        cls._instance[key] = super().__call__(*args, **kwargs)
      return cls._instance[key]


# 实现单例的装饰器
def singleton(cls):
  instances = {}

  def get_instance(*args, **kwargs):
    if cls not in instances:
      instances[cls] = cls(*args, **kwargs)
    return instances[cls]

  return get_instance




def excuteCmd(cmd):
  import subprocess
  ex = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
  out, err = ex.communicate()
  status = ex.wait()
  return status, out, err


def get_hour_minute_second():
  from datetime import datetime

  # 获取当前时间
  now = datetime.now()

  # 提取小时、分钟、秒和毫秒
  hours = now.hour
  minutes = now.minute
  seconds = now.second
  milliseconds = now.microsecond // 1000  # 将微秒转换为毫秒

  # 组合成表示从小时开始的部分
  result = f"{hours:02d}{minutes:02d}{seconds:02d}{milliseconds:03d}"
  return result


def custom_secure_filename(filename):
  # 保留中文字符，只移除不安全的字符
  return ''.join(c for c in filename if c.isalnum() or c in '._-')


def get_file_xxhash(file_name):
  import xxhash
  x = xxhash.xxh128()
  with open(file_name, 'rb') as fobj:
    while True:
      data = fobj.read(40960)
      if not data:
        break
      x.update(data)
  return x.hexdigest()


def remove_leading_slash(path):
  # 去掉路径中的前置斜杠
  if path.startswith('/') or path.startswith('\\'):
    return path[1:]
  return path


def get_file_list_in_zipfile(zip_file_path):
  import zipfile
  with zipfile.ZipFile(zip_file_path, 'r') as zip_file:
    # 获取文件列表
    file_list = zip_file.namelist()
    file_list2 = []
    for file_name in file_list:
      # 尝试将文件名解码为 UTF-8
      try:
        # 先用 cp437 解码
        decoded_name = file_name.encode('cp437').decode('utf-8')
      except UnicodeDecodeError:
        # 如果失败，尝试用 gbk 解码
        decoded_name = file_name.encode('cp437').decode('gbk', errors='replace')
      file_list2.append(decoded_name)
    return file_list2


def extract_zip_file2(zip_file, unzip_dir, password=None):
  import zipfile

  # 打开压缩文件
  with zipfile.ZipFile(zip_file, 'r') as zip_ref:
    # 获取文件列表
    info_list = zip_ref.infolist()
    # 遍历列表,解压文件并修改文件名编码
    for info in info_list:
      # 修改文件名编码
      try:
        info.filename = info.filename.encode('cp437').decode('gbk')
      except UnicodeDecodeError:
        info.filename = info.filename.encode('cp437').decode('utf-8')

      # 解压文件
      if password:
        zip_ref.extract(info, path=unzip_dir, pwd=password.encode('utf-8'))
      else:
        zip_ref.extract(info, path=unzip_dir)


def extract_zip_file_by_7za(zip_file, unzip_dir, password=None):
  if password:
    cmd_7za = f"""7za x -p{password} {zip_file} -o{unzip_dir}"""
  else:
    cmd_7za = f"""7za x  {zip_file} -o{unzip_dir}"""
  exit_code = os.system(cmd_7za)
  if exit_code != 0:
    raise Exception(f"解压文件 {zip_file} 失败，请检查密码或是否安装 7za 软件")


def merge_lists(*lists):
  """   合并多个列表，返回一个新的列表。   """
  return sum(lists, [])


def dim2List_to_dim1List(dim2List):
  """   将二维列表转换为一维列表。   """
  return [item for sublist in dim2List for item in sublist]


def convert_to_pinyin(path):
  from pypinyin import pinyin, Style
  # 将路径中的每个字符转换为拼音
  pinyin_list = pinyin(path, style=Style.NORMAL)

  # 将拼音列表拼接成一个字符串
  pinyin_path = ''.join([item[0] for item in pinyin_list])

  return pinyin_path


def get_leaf_nodes(obj, parent_key=''):
  """
  递归获取复杂对象的叶子节点的属性名和属性值。

  :param obj: 要遍历的对象
  :param parent_key: 当前属性的父键，用于构建完整的键路径
  :return: 包含叶子节点属性名和属性值的字典
  """
  if isinstance(obj, dict):
    for key, value in obj.items():
      new_key = f"{parent_key}.{key}" if parent_key else key
      yield from get_leaf_nodes(value, new_key)
  elif isinstance(obj, list):
    for index, value in enumerate(obj):
      new_key = f"{parent_key}[{index}]"
      yield from get_leaf_nodes(value, new_key)
  else:
    yield parent_key, obj

def get_leaf_nodes_old(obj, parent_key=''):
  """
  递归获取复杂对象的叶子节点的属性名和属性值。

  :param obj: 要遍历的对象
  :param parent_key: 当前属性的父键，用于构建完整的键路径
  :return: 包含叶子节点属性名和属性值的字典
  """
  leaf_nodes = {}

  if isinstance(obj, dict):
    for key, value in obj.items():
      new_key = f"{parent_key}.{key}" if parent_key else key
      leaf_nodes.update(get_leaf_nodes(value, new_key))
  elif isinstance(obj, list):
    for index, value in enumerate(obj):
      new_key = f"{parent_key}[{index}]"
      leaf_nodes.update(get_leaf_nodes(value, new_key))
  else:
    leaf_nodes[parent_key] = obj

  return leaf_nodes


class Normalization_date:
  def __init__(self):
    super().__init__()
    self.p1 = r'(20\d{2})-(\d{1,2})-(\d{1,2})(\d{1,2}:\d{1,2}:\d{1,2})'  # 2022-01-0112:30:00
    self.p2 = r'(20\d{2})/(\d{1,2})/(\d{1,2})(\d{1,2}:\d{1,2}:\d{1,2})'  # 2022/01/0112:30:00
    self.p3 = r'(20\d{2})-(\d{1,2})-(\d{1,2})(\d{1,2}:\d{1,2})'  # 2022-01-0112:30
    self.p4 = r'(20\d{2})/(\d{1,2})/(\d{1,2})(\d{1,2}:\d{1,2})'  # 2022/01/0112:30
    self.patterns = [self.p1, self.p2, self.p3, self.p4]

  def parse_datetime(self, s):
    import re
    s1 = s
    for pattern in self.patterns:
      s1 = re.sub(pattern, r'\1-\2-\3 \4', s1)
    return s1

class Normalization_med_word:
  """ 对一些关键的医学词汇进行归一化处理，如
  主 诉 --> 主诉
  诊 断 --> 诊断
  """

  def __init__(self):
    super().__init__()
    self.patterns = {
      r"(主\s*诉)": "主诉",
      r"(病\s*史)": "病史",
      r"(病\s*情)": "病情",
      r"(病\s*史)": "病史",
      r"(诊\s*断)": "诊断"
    }

  def normalize(self, s):
    import re
    s1 = s
    for pattern, replacement in self.patterns.items():
      s1 = re.sub(pattern, replacement, s1)
    return s1


def normalization_date(s):
  pattern = r'(\d{4})-(\d{2})-(\d{2})(\d{2}:\d{2}:\d{2})'
  pass


def get_ipv4_addresses():
  import socket
  """获取本机所有 IPv4 内网地址"""
  hostname = socket.gethostname()
  addresses = socket.gethostbyname_ex(hostname)[2]
  # 过滤掉本地回环地址 (127.0.0.1)
  return [addr for addr in addresses if not addr.startswith("127.")]

# 全角转半角
def strQ2B(ustring):
  ss = []
  for s in ustring:
    rstring = ""
    for uchar in s:
      inside_code = ord(uchar)
      if inside_code == 12288:
        inside_code = 32
      elif (inside_code >= 65281 and inside_code <= 65374):
        inside_code -= 65248
      rstring += chr(inside_code)
    ss.append(rstring)
  return ''.join(ss)

def fuzzy_match(str1, str2):
  matcher = difflib.SequenceMatcher(None, str1, str2)
  similarity = matcher.ratio()
  return similarity

def is_whole_str_wipe(txt):
  """ 如果一个待抹图的字符串长度小于15，且包含“签名”或“签字”字样，则认为是整体抹除 """
  btn = len(txt) <= 15 and '签名' in txt or '签字' in txt
  return btn


def time_count(func):
  import time
  def wrapper(*args, **kwargs):
    start_time = time.time()
    result = func(*args, **kwargs)
    used_time = time.time() - start_time
    print(f"函数【{func.__name__}】运行时间为{used_time}秒")
    return result

  return wrapper

def get_db_connstr_4sqlalchemy(mysql_conf):
  return f"mysql+mysqlconnector://{mysql_conf['datasource.username']}:{mysql_conf['datasource.password']}@{mysql_conf['datasource.host']}:{mysql_conf['datasource.port']}/{mysql_conf['datasource.db']}"

from sqlitedict import SqliteDict
def save_key_value_to_sqlitedict(key, value, tbl_name='comm1' , dbpath=None):
  dbpath =dbpath if dbpath else os.path.abspath(os.path.join(os.path.dirname(__file__), "../../app_dict.sqlite"))
  with SqliteDict(dbpath, tablename=tbl_name, encode=json.dumps, decode=json.loads, autocommit=True) as sdd:
    sdd[key] = value

def get_value_from_sqlitedict(key, tbl_name='comm1', dbpath=None):
  dbpath =dbpath if dbpath else os.path.abspath(os.path.join(os.path.dirname(__file__), "../../app_dict.sqlite"))
  with SqliteDict(dbpath, tablename=tbl_name, encode=json.dumps, decode=json.loads) as sdd:
    return sdd.get(key)


def encrypt_password(password_plaintext, key: str = None):
  from cryptography.fernet import Fernet
  import os

  # 如果未传入密钥，则从环境变量获取
  if key is None:
    key = os.getenv("DSCONF_ENC_KEY")
    if not key:
      raise Exception("DSCONF_ENC_KEY not found")

  cipher_suite = Fernet(key)
  password_encrypted = cipher_suite.encrypt(password_plaintext.encode())
  return password_encrypted

def decryp_password(key, password_encrypted):
  from cryptography.fernet import Fernet
  import os
  key = os.getenv("DSCONF_ENC_KEY")
  if not key:
    raise Exception("DSCONF_ENC_KEY not found")
  cipher_suite = Fernet(key)  # 密钥通过环境变量注入
  password_decrypted = cipher_suite.decrypt(password_encrypted).decode()
  return password_decrypted

def complete_datetime_string(input_str):
  # 构建完整格式的字符串输出（缺失的部分自动为00）
  from datetime import datetime

  # 定义可能的格式
  formats = [
    "%Y-%m-%d %H:%M:%S",  # 完整格式
    "%Y-%m-%d %H:%M",     # 缺少秒
    "%Y-%m-%d %H",        # 缺少分钟和秒
    "%Y-%m-%d",           # 只有日期
  ]

  # 尝试解析输入字符串
  dt = None
  for fmt in formats:
    try:
      dt = datetime.strptime(input_str, fmt)
      break
    except ValueError:
      continue

  if dt is None:
    raise ValueError("输入的日期格式不正确")

  return dt.strftime("%Y-%m-%d %H:%M:%S")

def test_extract_zip_file2():
  zip_file_path = r'V:\pdf_wipe\20240810\withpassword.zip'
  unzip_dir = r'V:\out\unzip'
  import time
  time1 = time.time()
  extract_zip_file_by_7za(zip_file_path, unzip_dir, password='abc')
  logger.info(f"解压耗时：{time.time() - time1}秒")

def test_save_and_get_value():
  key = "test_key"
  value = {"name": "Alice", "age": 30}
  value = "202401:bc:1"
  value = 1
  tbl_name = "test_table"

  # 测试保存和读取
  save_key_value_to_sqlitedict(key, value, tbl_name=tbl_name)
  retrieved_value = get_value_from_sqlitedict(key, tbl_name=tbl_name)

  assert(retrieved_value== value)

if __name__ == '__main__':
  # test_extract_zip_file2()
  test_save_and_get_value()
  # test_normalization_date()
  # test_med_word_normalization()
  # test_DbHelper_with_pool()
